﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using Kyoh.Lib.Component;
using System.Windows.Forms;

namespace Kyoh.App.Crawler.Contents
{
    internal abstract partial class SeedListComponent<TSeed> : Component, ISupportInitialize, IListSource
        where TSeed : class, ISeed, new()
    {
        protected SeedListComponent() { }
        protected SeedListComponent(IContainer container) { container.Add(this); }

        [Serializable]
        protected class SeedList : VolatileBindingSource<TSeed>
        {
            public SeedList() { }
            protected SeedList(SerializationInfo info, StreamingContext context)
                : base(info, context)
            {
            }
            public override PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
            {
                return base.GetItemProperties(listAccessors).Sort(new PropertyDescriptorComparer());
            }
        }

        protected readonly SeedList List;

        protected SeedListComponent(string saveName)
        {
            InitializeComponent();
            Parallelism = 1;
            SaveName = saveName;
            List = LoadList();
            List.Sync(WindowsFormsSynchronizationContext.Current);
        }

        protected SeedListComponent(IContainer container, string saveName)
        {
            container.Add(this);

            InitializeComponent();
            Parallelism = 1;
            SaveName = saveName;
            List = LoadList();
            List.Sync(WindowsFormsSynchronizationContext.Current);
        }

        /// <summary>情報を保存するファイル名。</summary>
        public string SaveName { get; private set; }
        private readonly BinaryFormatter _formatter = new BinaryFormatter();

        #region リストインターフェース
        public event ListChangedEventHandler ListChanged
        {
            add { if (value != null) List.ListChanged += value; }
            remove { if (value != null) List.ListChanged -= value; }
        }

        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public int Count { get { return List.Count; } }
        private readonly object _contentUriLocker = new object();
        private readonly Dictionary<string, int> _contents = new Dictionary<string, int>();
        private bool ContainsKey(TSeed seed)
        {
            if (seed != null && seed.UniqueKey != null)
                lock (_contentUriLocker)
                    return _contents.ContainsKey(seed.UniqueKey) && _contents[seed.UniqueKey] > 0;
            return false;
        }

        private void AddKey(TSeed seed)
        {
            if (seed != null && seed.UniqueKey != null)
            {
                lock (_contentUriLocker)
                {
                    if (_contents.ContainsKey(seed.UniqueKey))
                        _contents[seed.UniqueKey]++;
                    else
                        _contents.Add(seed.UniqueKey, 1);
                }
            }
        }
        private void RemoveKey(TSeed seed)
        {
            lock (_contentUriLocker)
            {
                if (seed.UniqueKey != null && _contents.ContainsKey(seed.UniqueKey))
                {
                    _contents[seed.UniqueKey]--;
                    if (_contents[seed.UniqueKey] == 0)
                        _contents.Remove(seed.UniqueKey);
                }
            }
        }
        public void Add(TSeed seed)
        {
            if (seed == null)
                return;
            if (ContainsKey(seed))
            {
                Trace.TraceWarning(Properties.Strings.WarnigDuplicatedContent, seed.UniqueKey);
                return;
            }
            seed.PropertyChanging += LoaderPropertyChanging;
            seed.PropertyChanged += LoaderPropertyChanged;
            List.Add(seed);
            AddKey(seed);
            DoWork();
        }
        public void Remove(TSeed seed)
        {
            if (seed == null)
                return;

            Cancel(seed);

            seed.PropertyChanging -= LoaderPropertyChanging;
            seed.PropertyChanged -= LoaderPropertyChanged;
            List.Remove(seed);

            RemoveKey(seed);
        }
        #endregion

        #region ISupportInitialize メンバー
        public void BeginInit()
        {
        }
        public void EndInit()
        {
            if (!DesignMode)
            {
                seedBackgroundWorker.RunWorkerAsync();
                workerTimer.Start();
            }
        }
        #endregion

        private static readonly Dictionary<string, SeedList> SeedListCache = new Dictionary<string,SeedListComponent<TSeed>.SeedList>(); 
        private SeedList LoadList()
        {
            var seedListPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), SaveName);
            if (SeedListCache.ContainsKey(seedListPath))
                return SeedListCache[seedListPath];

            try
            {
                using (var stream = new FileStream(seedListPath, FileMode.Open))
                {
                    var list = (SeedList) _formatter.Deserialize(stream);
                    SeedListCache.Add(seedListPath, list);
                    return list;
                }
            }
            catch (Exception exc)
            {
                Trace.TraceError(Properties.Strings.ErrorLoadSeedList, Properties.Strings.Seed, seedListPath);
                Debug.WriteLine(exc);
                var list = new SeedList();
                SeedListCache.Add(seedListPath, list);
                return list;
            }
        }

        /// <summary>リストを保存します。</summary>
        public void SaveList()
        {
            if (List != null)
            {
                var seedListPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), SaveName);
                try
                {
                    using (var stream = new FileStream(seedListPath, FileMode.Create))
                        _formatter.Serialize(stream, List);
                }
                catch (Exception exc)
                {
                    Trace.TraceError(Properties.Strings.ErrorSaveSeedList, seedListPath);
                    Debug.WriteLine(exc);
                }
            }
        }

        public void DoWork()
        {
            _waitHandle.Set();
        }

        private void WorkerTimerTick(object sender, EventArgs e)
        {
            DoWork();
        }

        private void SeedBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            Debug.WriteLine(SaveName + ": worker_DoWork");
            DoWork();
            Worker();
        }

        private readonly ManualResetEvent _waitHandle = new ManualResetEvent(false);
        private readonly CancellationTokenSource _cancellation = new CancellationTokenSource();

        private bool _listChanged;

        private void LoaderPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Priority":
                    lock (List)
                        _listChanged = true;
                    break;
                case "UniqueKey":
                    AddKey(sender as TSeed);
                    break;
            }
        }

        private void LoaderPropertyChanging(object sender, PropertyChangingEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "UniqueKey":
                    RemoveKey(sender as TSeed);
                    break;
            }
        }

        private IEnumerable<TSeed> GetSeeds()
        {
            while (true)
            {
                lock (_waitHandle)
                {
                    _waitHandle.WaitOne();
                    _waitHandle.Reset();
                }
                var list = List.Where(l => !l.Run).ToList();
                list.Sort((l1, l2) => l2.Priority.CompareTo(l1.Priority));
                foreach (var l in list)
                {
                    yield return l;
                    lock (list)
                    {
                        if (_listChanged)
                        {
                            Debug.WriteLine(SaveName + ": list_changed");
                            _listChanged = false;
                            DoWork();
                            break;
                        }
                    }
                }
            }
        }

        protected abstract void RunCore(TSeed seed);
        private void Run(TSeed seed)
        {
            try
            {
                RunCore(seed);
            }
            catch (OperationCanceledException cancel)
            {
                Trace.TraceInformation(Properties.Strings.InformationCancelled);
                Debug.Write(SaveName + ": Worker - ");
                Debug.WriteLine(cancel);
            }
            catch (Exception exc)
            {
                Trace.TraceError(Properties.Strings.ErrorAsyncWorker, exc.Message);
                Debug.Write(SaveName + ": Worker - ");
                Debug.WriteLine(exc);
            }
        }
        protected abstract void Cancel(TSeed seed);

        private void Worker()
        {
            while (true)
            {
                try
                {
                    GetSeeds()
                        .AsParallel()
                        .WithDegreeOfParallelism(Math.Min(Math.Max(Parallelism, 1), 10))
                        .WithCancellation(_cancellation.Token)
                        .ForAll(Run);
                }
                catch (OperationCanceledException cancel)
                {
                    Trace.TraceInformation(Properties.Strings.InformationCancelled);
                    Debug.Write(SaveName + ": Worker - ");
                    Debug.WriteLine(cancel);
                }
                catch (Exception exc)
                {
                    Trace.TraceError(Properties.Strings.ErrorAsyncWorker, exc.Message);
                    Debug.Write(SaveName + ": Worker - ");
                    Debug.WriteLine(exc);
                }
            }
        }

        [DefaultValue(1)]
        public int Parallelism { get; set; }
        [DefaultValue(5000)]
        public int WorkerInterval
        {
            get { return workerTimer.Interval; }
            set { workerTimer.Interval = value; }
        }

        #region IListSource メンバー

        public bool ContainsListCollection
        {
            get { return true; }
        }

        public System.Collections.IList GetList()
        {
            return List;
        }

        #endregion
    }
}